import math
import numpy as np
# from sklearn.linear_model import LinearRegression

from Core.Gadget import *

# ---标准算法---
def CalcSimpleAverageGrowthRate(value, base, period=1):
    if base == 0:
        raw_growth_rate = 0
    else:
        raw_growth_rate = (value - base) / abs(base)
        raw_growth_rate = raw_growth_rate / period
    #
    if value > 0 and base < 0:
        return raw_growth_rate, "P"
    #
    if value < 0 and base > 0:
        return raw_growth_rate, "L"
    #
    return raw_growth_rate, "N"


# ---已停止使用---
def CalcCompoundAnnualGrowthRate(value, base, period=1):
    if base == 0:
        raw_growth_rate = 0
    # elif base > 0 and value < 0:
    #     raw_growth_rate = -1
    else:
        power = 1 / period
        tmp_rate = (value - base) / abs(base)

        if tmp_rate < -1:
            raw_growth_rate = -1
        else:
            # print(tmp_rate)
            # s = np.sign(tmp_rate)
            # raw_growth_rate = math.pow(abs(tmp_rate) + 1, power) - 1
            # raw_growth_rate = s * raw_growth_rate
            raw_growth_rate = math.pow(tmp_rate + 1, power) - 1

    #
    if value > 0 and base < 0:
        return raw_growth_rate, "P"
    #
    if value < 0 and base > 0:
        return raw_growth_rate, "L"
    #
    return raw_growth_rate, "N"


def CalcRDSkew(barSeries, beginIndex, endIndex):
    lastBar = None
    sum2 = []
    sum3 = []
    lastClose = 0
    for index in range(beginIndex, endIndex+1):
        bar = barSeries[index]
        curDateTime = bar["StdDateTime"]
        curDateTime = ToLocalDateTime(curDateTime)
        #print(str(curDateTime) + " " + str(bar["Open"]) + " " + str(bar["Close"]))
        #print(curDateTime)
        if lastBar == None:
            lastClose = bar["Open"]
            lastBar = bar

        if curDateTime.minute%5 == 0:
            ret = 0
            if math.isnan(bar["Close"]) or math.isnan(lastBar["Close"]):
                ret = 0
            else:
                close2 =  bar["Close"]
                close1 = lastClose
                ret = math.log(close2/close1, math.e)
            sum2.append(ret * ret)
            sum3.append(ret * ret * ret)
            #print(str(curDateTime) + " " + str(ret))
            lastClose = bar["Close"]
            lastBar = bar
    #End of Loop Bars

    RDVar = np.sum(sum2)
    sum3Value = np.sum(sum3)
    if RDVar == 0:
        print("CalcRDSkew Value == 0")
        return 0
    RDSkew = 6.928203 * sum3Value / math.pow(RDVar, 1.5);
    #a1 = 6.928203 * sum3Value
    #a2 = math.pow(RDVar, 1.5)
    return RDSkew


def CalcVolatilityFunc(instrument, database, datetime1, datetime2):
    if datetime1 < instrument["IPO_Date"] or ("Delist_Date" in instrument and datetime2 > instrument["Delist_Date"]):
        print("Not Necessary Data Calc Factor Value @ " + instrument["Symbol"])

    if instrument["Symbol"] == "000159.SZ":
        kkwood = 1
    barSeries = database.getDataSeries(instrument["Symbol"] + "_Time_86400_Bar",datetime1,datetime2)
    stdDev = CalcVolatility(barSeries)
    return stdDev


def CalcVolatility(barSeries, index, length):

    retValues = []
    for i in range(1,length):
        last = index - length + i
        current = index - length + 1 + i
        lastBar = barSeries[last]
        curBar = barSeries[current]

        if curBar["TradeStatus"] == "停牌一天" or lastBar["TradeStatus"] == "停牌一天":
            kkwood = 1
        else:
            closeLag = lastBar["Values"]["BClose"]
            close    = curBar["Values"]["BClose"]
            if close == 0 or closeLag == 0:
                print("LogRet Encounter Zero Price")
                return []
            ret = math.log(close/closeLag, math.e)
            retValues.append(ret)

    if retValues.__len__() == 0:
        print("Can't Calc Volatility")
        return None
    elif retValues.__len__()/(length-1) < 0.8:
        print("Too Less Data")
        return None
    else:
        stdDev = np.var(retValues)
        stdDev = np.sqrt(stdDev)
        return stdDev


def CalcVolatility(barSeries, minDataPercentage = 0.8):

    retValues = []
    for i in range(1,barSeries.count()):
        lastBar = barSeries[i-1]
        curBar = barSeries[i]

        if curBar["TradeStatus"] == "停牌一天" or lastBar["TradeStatus"] == "停牌一天":
            kkwood = 1
        else:
            closeLag = lastBar["Values"]["BClose"]
            close    = curBar["Values"]["BClose"]
            if close == 0 or closeLag == 0:
                print("LogRet Encounter Zero Price")
                return []
            ret = math.log(close/closeLag, math.e)
            retValues.append(ret)

    if retValues.__len__() == 0:
        print("Zero Valid Data")
        return None
    elif retValues.__len__()/(barSeries.count() -1) < minDataPercentage:
        print("Too Less Valid Data")
        return None
    else:
        stdDev = np.var(retValues)
        stdDev = np.sqrt(stdDev)
        return stdDev

def CalcVolatility_DataSeries(dataSeries, fieldName):

    retValues = []
    for i in range(1,len(dataSeries)):
        closeLag = dataSeries[i-1][fieldName]
        close = dataSeries[i][fieldName]
        #ret = math.log(close/closeLag, math.e)
        ret = (close / closeLag) - 1
        retValues.append(ret)
    #
    if retValues.__len__() == 0:
        print("Zero Valid Data")
        return None
    else:
        stdDev = np.var(retValues)
        stdDev = np.sqrt(stdDev)
        return stdDev

def CalcTrailing(symbol, database, datetime1, datetime2):
        closePrice = []
        highPrice = []
        barSeries = database.getDataSeries(symbol + "_Time_86400_Bar",datetime1,datetime2)
        for bar in barSeries:
            closePrice.append(bar["Values"]["BClose"])
            highPrice.append(bar["Values"]["BHigh"])
        hiPrice = np.max(closePrice)
        trailing = closePrice[closePrice.__len__()-1] / hiPrice -1
        return trailing

def CalcRangeReturn(symbol,database, datetime1,datetime2, isLog = False):
    barSeries = database.getDataSeries(symbol + "_Time_86400_Bar",datetime1,datetime2)
    if barSeries.count() < 2:
        print("No range data to Calc Return " + symbol)
        return None

    close1 = barSeries[0]["Values"]["BClose"]
    close2 = barSeries[barSeries.count()-1]["Values"]["BClose"]

    if isLog:
        ret = math.log(close2/close1, math.e)
    else:
        ret = close2/close1 - 1

    return ret

def CalcCorrelation(database, barSeries1, barSeries2):


    if barSeries1.count() == 0 or barSeries2.count() == 0:
        return None

    if barSeries1.count() != barSeries2.count():
        return None

    count = barSeries1.count()

    rets1 = []
    rets2 = []
    tradeCount1 = 0
    tradeCount2 = 0
    for i in range(count):
        if barSeries1[i]["TradeStatus"] != "停牌一天" :
            tradeCount1 = tradeCount1 + 1

        if barSeries2[i]["TradeStatus"] != "停牌一天" :
            tradeCount2 = tradeCount2 + 1

        if i < 1:
            continue

        ret = math.log(barSeries1[i]["Values"]["BClose"]/barSeries1[i - 1]["Values"]["BClose"], math.e)
        rets1.append(ret)
        ret = math.log(barSeries2[i]["Values"]["BClose"]/barSeries2[i - 1]["Values"]["BClose"], math.e)
        rets2.append(ret)

    if tradeCount1 / count < 0.5 or tradeCount2 / count < 0.5:
        return None

    #print(rets1)
    #print("   ")
    #print(rets2)
    result = np.corrcoef(rets1,rets2)
    return result[0][1]

#---乱lastBar入一段计算基准收益的代码---
def CalcIndustryReturn(datetime1, datetime2, database):

    instrumentsBySymbol = {}
    instruments = LoadInstruments(database,instrumentsBySymbol, 0)

    insdustries = []
    for instrument in instruments:
        if instrument.industry not in insdustries and instrument.industry != "":
            insdustries.append(instrument.industry)

    retsByIndustry = {}
    for industry in insdustries:
        retsByIndustry[industry] = []
    retsByIndustry["HS300"] = []

    bmBarSeries = database.getDataSeries("000300.SH_Time_86400_Bar",datetime1,datetime2)
    lastDatetime = datetime1
    atMonthBeginDatetime = datetime1
    for bar in bmBarSeries:
        curDatetime = bar["StdDateTime"]
        #At New Month
        if curDatetime.month != lastDatetime.month:
            print("Calc Ret @ Date: " + str(curDatetime))
            for industry in insdustries:#Loop Industry
                stockRets = []
                for instrument in instruments:#Loop Instrument
                    if instrument.industry == industry:
                        barSeries = database.getDataSeries(instrument.symbol + "_Time_86400_Bar",atMonthBeginDatetime,lastDatetime)
                        if(barSeries.count() == 0):
                            continue
                        firstBar = barSeries[0]
                        lastBar = barSeries[barSeries.count()-1]
                        if firstBar["TradeStatus"] == "停牌一天" or lastBar["TradeStatus"] == "停牌一天":
                            continue
                        ret = lastBar["Values"]["BClose"] / firstBar["Values"]["BClose"] - 1
                        stockRets.append(ret)
                mean = np.mean(stockRets)
                retsByIndustry[industry].append([lastDatetime,mean])
                print("Loop Industry: " + industry + " " + str(mean))
                kkwood = 0
            #Fill BM Return
            barSeries = database.getDataSeries("000300.SH_Time_86400_Bar",atMonthBeginDatetime,lastDatetime)
            firstBar = barSeries[0]
            lastBar = barSeries[barSeries.count()-1]
            ret = lastBar["Values"]["BClose"] / firstBar["Values"]["BClose"] - 1
            retsByIndustry["HS300"].append([lastDatetime,ret])
            #Update monthBeginDateTime
            atMonthBeginDatetime = lastDatetime
            kkwood = 1
        lastDatetime = curDatetime
        kkwood = 2

    industryRet = []
    count = retsByIndustry["HS300"].__len__()
    for i in range(count):
        entry = []
        entry.append(retsByIndustry["HS300"][i][0])
        entry.append(retsByIndustry["HS300"][i][1])
        for industry in insdustries:
            #print(industry)
            entry.append(retsByIndustry[industry][i][1])
        industryRet.append(entry)

    ExportList2ToFile("D:/IndustryReturn.csv",industryRet)

def CalcPortfolioReturn(rebanlanceList_Col0isDatetime_Col2isInstrumentList):
    database = MongoDB("192.168.1.90","27017")
    rebalances = rebanlanceList_Col0isDatetime_Col2isInstrumentList
    count = rebanlanceList_Col0isDatetime_Col2isInstrumentList.__len__()
    instrumentsIoTrade = []
    datetime1 = datetime(2005,1,1,15,0,0)
    datetime2 = datetime(2005,1,1,15,0,0)
    portfolioRet = []
    for i in range(count + 1):
        if i == 0:
            datetime1 = rebalances[i][0]
            instrumentsIoTrade = rebalances[i][1]

        else:
            rets = []
            datetime2 = rebalances[i][0]
            for symbol in instrumentsIoTrade:
                barSeries = database.getDataSeries(symbol +"_Time_86400_Bar",datetime1,datetime2)
                ret = 0
                if barSeries.count() != 0 and barSeries[0]["Values"]["BClose"] != 0:
                    ret = barSeries[barSeries.count()-1]["Values"]["BClose"] / barSeries[0]["Values"]["BClose"] - 1
                rets.append(ret)#记录一下上期每只入选股票的区间收益
            bmBarSeries = database.getDataSeries("000300.SH_Time_86400_Bar",datetime1,datetime2)
            bmRet = bmBarSeries[bmBarSeries.count()-1]["Values"]["BClose"] / bmBarSeries[0]["Values"]["BClose"] - 1
            portfolioRet.append([datetime2, np.mean(rets), bmRet])
            datetime1 = datetime2

            if i <= count:
                instrumentsIoTrade = rebalances[i][1]

def CheckAlphaTrend(marketRets, stockRets):
    if marketRets.__len__() != stockRets.__len__():
        print("CheckAlphaReversion: Input not Equal Length")
        return
    y = []
    x = []
    dailyAlpha = []
    for i in range(marketRets.__len__()):
        alpha = stockRets[i] - marketRets[i]
        dailyAlpha.append(alpha)
        if i == 0:
            y.append(dailyAlpha[0])
        else:
            y.append(dailyAlpha[i] + y[i-1])
        x.append([i])

    linreg =LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
    model = linreg.fit(x, y)

    #---
    f = linreg.predict(x)
    yMean = np.mean(y)
    sst = 0
    ssr = 0
    sse = 0
    for i in range(marketRets.__len__()):
        sst = sst + (y[i] - yMean) * (y[i] - yMean)
        ssr = ssr + (f[i] - yMean) * (f[i] - yMean)
        sse = sse + (y[i] - f[i]) * (y[i] - f[i])

    output = {}
    output["RSquare"] = 1- (sse / sst)
    output["Intercapet"] = model.intercept_
    output["Slope"] = model.coef_[0]
    output["Mean"] = np.mean(y)
    output["StdDev"] = np.std(y)

    return output

# ---index is Start Point, length is backward length---
def CalcLogRet(barSeries, index, length):
    rets = []
    count = barSeries.Count()

    if length > index:
        print("LogRet Not Enough Data: length > index")
        return rets

    for i in range(length):
        if(i == 238):
            kkwood = 0
        a1 = index - length + i
        a2 = index - length + 1 + i
        bar1 = barSeries[a1]
        bar2 = barSeries[a2]
        closeLag = barSeries[index - length + i]["Values"]["BClose"]
        close    = barSeries[index - length + 1 + i]["Values"]["BClose"]
        if close == 0 or closeLag == 0:
            print("LogRet Encounter Zero Price")
            return []
        ret = math.log(close/closeLag, math.e)
        rets.append(ret)
    return rets

def CalcExetreme(barSeries, index, length, high = True):
    rets = []
    count = barSeries.count()
    if length > index:
        print("LogRet Not Enough Data: length > index")
        return rets

    extreme = barSeries[index - length]["Values"]["BClose"]
    for i in range(length):
        value = barSeries[index - length + i]["Values"]["BClose"]
        if high:
            if value > extreme:
                extreme = value
        else:
            if value < extreme:
                extreme = value

    return extreme

def SMA(barSeries, index, length):
    sum = 0
    for i in range(length):
        close = barSeries[index - i]["Values"]["BClose"]
        sum = sum + close
    return sum / length

def Breakout(barSeries, index , reference, crossover = True):
    close = barSeries[index]["Values"]["BClose"]
    closeLag = barSeries[index - 1]["Values"]["BClose"]
    if crossover:
        if closeLag <= reference and close > reference:
            return True
    else:
         if closeLag >= reference and close < reference:
            return True
    return False

def MaxDrawdown(dataSeries, fieldName):
    high = None
    mdd = 0
    for data in dataSeries:
        if high == None:
            high = data
        if data[fieldName] > high[fieldName]:
            high = data
        drawdown = data[fieldName] / high[fieldName] - 1
        if drawdown < mdd:
            mdd = drawdown

    return mdd


if __name__ == '__main__':
    v0 = -20
    v1 = -4
    v2 = -10

    # r1 = CalcSimpleAverageGrowthRate(v2, v1, 3)
    # print(r1)
    r2 = CalcCompoundAnnualGrowthRate(v2, v1, 3)
    print(r2)

    tmp_rate = -8.56443
    power = 0.3333
    raw_growth_rate = math.pow(tmp_rate + 1, power) - 1
    print(raw_growth_rate)
